
`include "common_header.verilog"

//  *************************************************************************
//  File : pcs_tx_lane
//  *************************************************************************
//  This program is controlled by a written license agreement.
//  Unauthorized reproduction or use is expressly prohibited.
//  Copyright (c) 2010 MorethanIP
//  Muenchner Strasse 199, 85757 Karlsfeld, Germany
//  info@morethanip.com
//  http://www.morethanip.com
//  *************************************************************************
//  Designed by : Thomas Schrobenhauser
//  info@morethanip.com
//  *************************************************************************
//  Description : PCS TX FEC, Buffer and Gearboxes for 1 lane
//  Version     : $Id: pcs_tx_lane.v,v 1.12 2014/02/05 10:05:10 ts Exp $
//  *************************************************************************

module pcs_tx_lane (

`ifdef MTIP_SDPM_GEN_GLOBAL_RST
   sdpm_core_reset,
   sdpm_bist_i,
   sdpm_bist_o,
`endif        
   reset_txclk,
   reset_sd_tx_clk,
   txclk,
   sd_tx_clk,
   sd_tx_clk_ena,
   tx_d,
   tx_d_wr,
   tx_bf_afull,
   tx_bf_full,
   sd_tx,
`ifdef MTIPPCS_FEC_ENA
   fec_ena,
`endif
`ifdef MTIPPCS_BS256_ENA
   bs256_tx_ena,
   bs256_txd,
   bs256_txd_wr,
`endif
   gb_bp_ena,
   sw_reset,
   sw_reset_wclk,
   sw_reset_rclk,
   tx_lane_thresh );

`include "mtip_pcs100g_pack_package.verilog"

`ifdef MTIP_SDPM_GEN_GLOBAL_RST
input   sdpm_core_reset;        // global memory reset for special memories
input   [`MTIP_SDPM_GEN_BIST_I:0] sdpm_bist_i;  // inputs to ALL memories but not necessarily all used in this instance.
output  [`MTIP_SDPM_GEN_BIST_O:0] sdpm_bist_o;  // outputs OR'ed from ALL memories when travelling up the hierarchies
`endif        
input   reset_txclk;            //  async active high reset
input   reset_sd_tx_clk;        //  async active high reset
input   txclk;                  //  system clock
input   sd_tx_clk;              //  Serdes clock
input   sd_tx_clk_ena;          //  Serdes Tx clock enable
input   [65:0] tx_d;            //  encoded datastream
input   tx_d_wr;                //  data valid (buffer write)
output  tx_bf_afull;            //  line buffer almost full (backpressure)
output  tx_bf_full;             //  line buffer full (overflow/underflow error)
output  [SERDES_WIDTH-1:0] sd_tx; //  data output
`ifdef MTIPPCS_FEC_ENA
input   fec_ena;                //  FEC Enable (system clock)
`endif
`ifdef MTIPPCS_BS256_ENA
input   bs256_tx_ena;           //  256-bit Bit-stream interface enable (cgmii_txclk domain)
output  [65:0] bs256_txd;       //  Bit-stream transmit data
output  bs256_txd_wr;           //  Bit-stream transmit data write
`endif
input   gb_bp_ena;              //  gearbox bypass enable
input   sw_reset;               //  Software Reset (rising edge, system clock)
output  sw_reset_wclk;          //  software reset controlled in write clock (system clock)
output  sw_reset_rclk;          //  software reset controlled in read clock (line clock)
input   [3:0] tx_lane_thresh;   //  4 bit per lane tx decoupling buffer level threshold (set 7 as a compromise if unsure)

wire    tx_bf_afull; 
wire    tx_bf_full; 
wire    [SERDES_WIDTH - 1:0] sd_tx; 
`ifdef MTIPPCS_BS256_ENA
wire    [65:0] bs256_txd;
wire    bs256_txd_wr;
`endif
wire    sw_reset_wclk; 
wire    sw_reset_rclk; 

wire    sreset_wclk; 
wire    sreset_rclk; 

//  FEC Encoder
`ifdef MTIPPCS_FEC_ENA
wire    fec_cen; 
wire    [65:0] fec_din_r; //  encoded datastream
wire    fec_cen_r; 
wire    [65:0] fec_dout; //  encoded datastream
`endif

//  FIFO write
wire    [65:0] ff_din; //  encoded datastream
wire    ff_wren; //  data valid (buffer write)
wire    ff_wren_m; //  data valid (buffer write) masked
wire    [65:0] ff_din_r; //  encoded datastream
wire    ff_wren_r; //  data valid (buffer write)
//  fifo read
wire    ff_rden; 
wire    [65:0] ff_dout; //  FIFO data input
wire    ff_afull; 
wire    ff_full; 
wire    ff_aempty; 
reg     ff_aempty_l; //  latched aempty to gearbox
wire    ff_empty; 
reg     [1:0] ff_empty_s;       //  history empty (pipe)
reg     ff_uflow_l;             //  latched underflow occurence in serdes domain
wire    ff_uflow_l_wclk;        //  synced to txclk
reg     [5:0] ff_uflow_cnt;     //  underflow wait counter
//  gearbox
wire    gb_rden; 
wire    [65:0] ff_dout_r; //  FIFO data output registered/stored
wire    gb_bp_ena_s; //  gearbox bypass enable, sync'ed to line clock

// afull pacer
wire    [3:0] wlevel_cmp;       //  4 bit per lane tx decoupling buffer level threshold
wire    [3:0] wlevel_cmp_2;     //  4 bit per lane tx decoupling buffer level threshold + 2

wire    ff_afull_int; 
reg     afull_mask;
reg     afull_mask_d;
wire    [3:0] wlevel;   // write side level 
reg     [3:0] wlevel_d; // write side level delayed
reg     wl_inc;         // wlevel increment indication

//  Sync reset control

ff_sreset U_RST (
          .reset_lclk(reset_txclk),
          .lclk(txclk),
          .lsreset_in(sw_reset),
          .lsreset_done(),
          .lsreset_out(sreset_wclk),
          .reset_oclk(reset_sd_tx_clk),
          .oclk(sd_tx_clk),
          .osreset_out(sreset_rclk));

assign sw_reset_wclk = sreset_wclk; 
assign sw_reset_rclk = sreset_rclk; 

//  --------------------
//  Optional FEC Encoder
//  --------------------
`ifdef MTIPPCS_FEC_ENA

        // add input register for timing.

assign fec_cen = tx_d_wr & fec_ena;     // disable FEC when not in use

mtip_dffvec #(67) U_FECIREG (
          .reset(reset_txclk),
          .clk(txclk),
          .i({fec_cen, tx_d}),
          .o({fec_cen_r, fec_din_r}));

cec1_66_2112el U_FECENC (
          .clk(txclk),
          .cen(fec_cen_r),
          .reset(reset_txclk),
          .D(fec_din_r),
          .Q(fec_dout));

assign ff_din  = fec_ena == 1'b 1 ? fec_dout  : tx_d; 
assign ff_wren = fec_ena == 1'b 1 ? fec_cen_r : tx_d_wr; 

`else

assign ff_din  = tx_d; 
assign ff_wren = tx_d_wr; 

`endif

`ifdef MTIPPCS_BS256_ENA

assign bs256_txd    = ff_din;
assign bs256_txd_wr = ff_wren & bs256_tx_ena;   // disable BS interface when not in use

`endif

`ifdef MTIPPCS_BS256_ENA

// suppress write to avoid read/write to same address at memory.
assign ff_wren_m = ff_wren & !sw_reset & !sreset_wclk & ~bs256_tx_ena;

`else

// suppress write to avoid read/write to same address at memory.
assign ff_wren_m = ff_wren & !sw_reset & !sreset_wclk;

`endif

// FIFO input registers (timing opt)
mtip_dffvec #(67) U_FFIREG (
          .reset(reset_txclk),
          .clk(txclk),
          .i({ff_wren_m, ff_din}),      // ff_wren
          .o({ff_wren_r, ff_din_r}));

//  --------------------
//  Write side Pacing
//  --------------------

assign wlevel_cmp   = tx_lane_thresh;
assign wlevel_cmp_2 = tx_lane_thresh + 4'd 2;


// In bypass mode, the read side reads continuously. Then the write side latency 
// reacting on afull is longer than the amount stored in the FIFO when using a 
// reference clock close to line clock.
// In this mode, the pacer slows down the write side instead of immediate switch off.
// Off completely is still required for e.g. EEE when the serdes
// is powered down and the read side stops. The FIFO then will
// fill up but we have enough margin to the top to avoid overflow.

always @*
begin
        if(wlevel < wlevel_cmp | (wlevel == wlevel_cmp & ff_wren_r==1'b 0))
        begin
                afull_mask = 1'b 0;    // allow write
        end
        else if( wlevel < wlevel_cmp_2 | (wlevel == wlevel_cmp_2 & ff_wren_r==1'b 0) )
        begin
                // a slow stop hysteresis but immediate start
                
                if( wl_inc==1'b 0 )
                begin
                        afull_mask = 1'b 0;     // if decrementing allow immediately
                end
                else
                begin
                        // stop for 1 cycle, then allow again
                        afull_mask = ~afull_mask_d;
                end
                
        end
        else // >wlevel_cmp_2
        begin
                afull_mask = 1'b 1;    // permanent stop
        end                                          
       
end

always @(posedge reset_txclk or posedge txclk)
begin
        if( reset_txclk==1'b 1 )
        begin
                afull_mask_d <= 1'b 1;
        end
        else
        begin
                afull_mask_d <= afull_mask;
        end
end

assign ff_afull = afull_mask; // ff_afull_int & afull_mask;

//
// indicate rising wlevel
//
always @(posedge reset_txclk or posedge txclk)
begin
        if( reset_txclk==1'b 1 )
        begin
                wlevel_d <= 4'b 0000;
                wl_inc   <= 1'b 0;
        end
        else
        begin
                wlevel_d <= wlevel;
                
                // if current < last then its not rising. If >= we mark it as "rising".
                // Also, when at or above cmp2 level it is always assumed "falling" 
                // to allow immediate write restart when level falls below cmp2 again
                // (afull_mask is then permanent 1).
                //
                // In bypass mode the rates can be close to each other so the wlevel can
                // be stable for several cycles making afull assertion too aggressive. Hence mark 
                // "falling" then also if it stays constant to mask afull if the level stays.
                
                if( (gb_bp_ena==1'b 0 & (wlevel < wlevel_d)) | 
                    (gb_bp_ena==1'b 1 & (wlevel <= wlevel_d) & (wlevel >= wlevel_cmp)) |
                    (wlevel >= wlevel_cmp_2) ) 
                begin
                        wl_inc <= 1'b 0;
                end
                else
                begin
                        wl_inc <= 1'b 1;
                end
                
        end
end



//  --------------------
//  Line FIFO
//  --------------------
af_fifo_1234wl_sr #(
                .FF_WIDTH(66),
                .ADDR_WIDTH(4),
                .DEPTH(16),
                .AF_THRESHOLD(8),
                .AE_THRESHOLD(4)) 
        U_FF (
        `ifdef MTIP_SDPM_GEN_GLOBAL_RST
          .sdpm_core_reset(sdpm_core_reset),
          .sdpm_bist_i(sdpm_bist_i),
          .sdpm_bist_o(sdpm_bist_o),
        `endif        
          .reset_wclk(reset_txclk),
          .reset_rclk(reset_sd_tx_clk),
          .sreset_wclk(sreset_wclk),
          .sreset_rclk(sreset_rclk),
          .wclk(txclk),
          .wren(ff_wren_r),
          .din(ff_din_r),
          .rclk(sd_tx_clk),
          .rden(ff_rden),
          .dout(ff_dout),
          .afull(ff_afull_int), // ff_afull
          .full(ff_full),
          .aempty(ff_aempty),
          .empty(ff_empty),
          .wlevel(wlevel));

assign ff_rden = gb_rden & sd_tx_clk_ena;

assign tx_bf_full  = ff_full | ff_uflow_l_wclk;  //  write clock domain
assign tx_bf_afull = ff_afull; //  write clock domain

//  -----------------------------------------------------------------------------
//  Read side underflow detection. 
//  When empty is seen on read side, underflow is latched and transferred to
//  write side to cause soft-reset eventually. latch is cleared by soft-reset only.
//  -----------------------------------------------------------------------------
always @(posedge reset_sd_tx_clk or posedge sd_tx_clk)
begin
        if (reset_sd_tx_clk == 1'b 1)
        begin
                ff_empty_s  <= 2'b 00;	
                ff_uflow_l  <= 1'b 0;
                ff_uflow_cnt <= 6'd 0;
        end
        else
        begin

                // keep short history of empty reached.
                
                ff_empty_s  <= {ff_empty_s[0], ff_empty};
                
                // protect underflow from acting after reset when starting up.
                
                if( sreset_rclk == 1'b 1 )
                begin
                        ff_uflow_cnt <= 6'd 0;
                end
                else if( ff_aempty_l==1'b 0 & ff_aempty==1'b 1 & ff_uflow_cnt != 6'b 111111 )
                begin
                        ff_uflow_cnt <= ff_uflow_cnt + 6'd 1;
                end
                
                // indicate underflow if empty has been reached at least twice within 3 clocks 
                // (filter out single cycle spikes)
                
                if (sreset_rclk == 1'b 1)
                begin
                        ff_uflow_l  <= 1'b 0;
                end
                else if (ff_aempty_l==1'b 0 & ff_uflow_cnt == 6'b 111111 & 
                         ff_empty == 1'b 1 & ff_empty_s!=2'b 00)
                begin
                        // if read has started (aempty_l=0) and it runs into empty 
                        // the fifo output can be corrupted from underflow.
                        
                        ff_uflow_l <= 1'b 1;	
                end

        end
end

// sync underflow bit into write clock domain
mtip_xsync #(1) U_SYUFLOW (
          .reset(reset_txclk),
          .clk(txclk),
          .data_in(ff_uflow_l),
          .data_s(ff_uflow_l_wclk));



//  -----------------------------------------------------------------------------
//  Combine FIFO read with clock enable compensating for the memory read latency.
//  implements the memory output register.
//  -----------------------------------------------------------------------------
r_fifo_rena_store #(66) U_FFSTORE (
          .reset_rclk(reset_sd_tx_clk),
          .rclk(sd_tx_clk),
          .rclk_ena(sd_tx_clk_ena),
          .rden(gb_rden),               //  same that goes to the memory
          .dout_mem(ff_dout),
          .dout(ff_dout_r),             //  output data, latched
          .dmem_val());

//  -----------------
//  Read side Gearbox
//  -----------------

// sync bypass bit
mtip_xsync #(1) U_BPS (
          .reset(reset_sd_tx_clk),
          .clk(sd_tx_clk),
          .data_in(gb_bp_ena),
          .data_s(gb_bp_ena_s));


//  almost empty is masked once read has started to avoid TX stops reading at any time.
//  only a soft-reset will stop and cause new restart.
always @(posedge reset_sd_tx_clk or posedge sd_tx_clk)
   begin : process_1
   if (reset_sd_tx_clk == 1'b 1)
      begin
      ff_aempty_l <= 1'b 1;	
      end
   else
      begin

      if (sreset_rclk == 1'b 1)
         begin
         ff_aempty_l <= 1'b 1;	
         end
      else if (ff_aempty == 1'b 0 )
         begin
         ff_aempty_l <= 1'b 0;	
         end

      end
   end

gearbox66_og1_bp #(

        .SERDES_WIDTH(SERDES_WIDTH),
        .IN_REGISTER(0), // 0: no input register
        .OUT_REGISTER(0) )      // 0: no output register, as toplevel will have one
                
        U_TXGB (

          .reset(reset_sd_tx_clk),
          .clk(sd_tx_clk),
          .clk_ena(sd_tx_clk_ena),
          .gb_bp_ena(gb_bp_ena_s),
          .ff_rd(gb_rden),
          .ff_din(ff_dout_r),
          .ff_aempty(ff_aempty_l),
          .data_out(sd_tx));

endmodule // module pcs_tx_lane

